深入ES6 (八) Generator与async

Generator

Generator函数算是一个状态机,或者说他是一个具有Iterator接口特性的函数。

1
2
3
4
5
6
7
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();

Generator函数与普通函数之间的区别有两点:

1:函数名前有个*

2:yield表达式

yield表达式

yield表达式是产出的意思,配合下面这块代码更好的理解yield表达式。

1
2
3
4
5
6
7
8
9
10
11
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

上面这块代码中,我们执行hw函数的next()方法,(实际是Iterator接口提供的next方法)使得该Generator输出yield定义的值

也就是说,在Generator中,每次执行next(),都会执行到yield表达式为止。

1
2
3
4
5
function* tezml() {
console.log("tezml")
}
let name = tezml();
name.next();//tezml

在Generator中,如果没有yield表达式,Generator就变成了一个普通函数,只是从直接调用变成 .next()调用。

1
2
3
4
function tezml(){
yield 1;
}
// SyntaxError: Unexpected number

另外yield表达式只能用在Generator中,用在普通函数中会报错

1
2
3
4
5
6
7
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}

yield表达式如果用在另一个表达式之中,必须放在圆括号里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* foo(x) {
var y = 2 * (yield (x + 1));//第一次next时,yield返回来的值是undefined,导致成 2 * undefined
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

Generator的遍历

如果只用next当然很不智能了,由于Generator函数具有Iterator特性,所以我们可以使用for…of来完成遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
function *foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
1
2
3
4
5
6
7
8
9
10
11
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }

Generator有个return方法来结束当前遍历,并接受一个参数来代替结束值

yield* 表达式

Generator既然是函数就避免不了调用,当我们涉及到Generator调用Generator时候就会用到yield*表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "y"

上面这块代码是调用不起来foo函数的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"

async

async可以理解为是Generator的语法糖。用处就是处理异步操作,其用法就是把Generator中的’*’换成了’async’,把yield替换成了await

1
2
3
4
5
6
var asyncReadFile = async function () {
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

async函数对 Generator 函数的有一些改进,具体在:

1、内置执行器

Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

1
asyncReadFile();

2、返回值是 Promise。

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

基本用法

1
2
3
4
5
6
7
8
9
async function getStockPriceByName(name) {
var symbol = await getStockSymbol(name);
var stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
console.log(result);
});

一些注意点

await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}

await命令只能用在async函数之中,如果用在普通函数,就会报错。

1
2
3
4
5
6
7
8
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}

async 函数的实现原理

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

1
2
3
4
5
6
7
8
9
10
11
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}